<?php
namespace Sfsn\Core\Model;

use Sfsn\Core\Util\Page;
use Sfsn\Core\Config;

/**
 * 数据驱动
 * 负责调用对应数据驱动，建立连接，处理执行sql
 *
 * @author shooke
 *        
 */
class Query
{

    /**
     * 从数据库操作对象
     *
     * @var unknown
     */
    public static $readLink = null;

    /**
     * 主数据库操作对象
     *
     * @var unknown
     */
    public static $writeLink = null;
    /**
     * 分页类对象
     * @var unknown
     */
    public static $page;
    /**
     * 从数据库
     *
     * @var unknown
     */
    public $slaveDb = false;

    /**
     * 缓存对象
     *
     * @var unknown
     */
    public $cache = null;

    /**
     * sql语句，主要用于输出构造成的sql语句
     *
     * @var unknown
     */
    public $sql = '';

    /**
     * 表前缀，主要用于在其他地方获取表前缀
     *
     * @var unknown
     */
    public $pre = '';

    /**
     * 配置
     *
     * @var unknown
     */
    public $config = [];

    /**
     * 查询表达式参数
     *
     * @var unknown
     */
    protected $options = [
        'field' => '*',
        'where' => []
    ];

    /**
     * 字段
     *
     * @var unknown
     */
    protected $field = [];

    /**
     * 构造函数
     *
     * @param unknown $config            
     */
    public function __construct($config = [])
    {
        $this->pre = $config['DB_PREFIX']; // 数据表前缀
        $this->slaveDb = ! empty($config['DB_SLAVE']); // 判断是否支持主从
        $this->config = $config; // 参数配置
    }

    /**
     * 获取从数据库链接
     *
     * @return \Sfsn\Core\Model\unknown
     */
    private function readLink()
    {
        if (isset(self::$readLink)) {
            return self::$readLink;
        } else {
            if (! $this->slaveDb) {
                return $this->writeLink();
            } else {
                $slave_count = count($this->config['DB_SLAVE']);
                // 遍历所有从机
                for ($i = 0; $i < $slave_count; $i ++) {
                    $db_all[] = array_merge($this->config, $this->config['DB_SLAVE'][$i]);
                }
                // 随机选择一台从机连接
                $rand = mt_rand(0, $slave_count - 1);
                array_unshift($db_all, $db_all[$rand]);
                foreach ($db_all as $config) {
                    self::$readLink = self::daoInstance($config, false);
                    if(self::$readLink->success()){
                        return self::$readLink;
                    }                    
                }
                // 如果全部没有链接成功，调用主数据库
                return $this->writeLink();
            }
        }
    }

    /**
     * 获取主数据库链接
     *
     * @return \Sfsn\Core\Model\unknown
     */
    private function writeLink()
    {
        if (isset(self::$writeLink)) {
            return self::$writeLink;
        } else {
            self::$writeLink = self::daoInstance($this->config);
            return self::$writeLink;           
        }
    }

    /**
     * 取得数据库类实例
     * 采用单例模式，每个配置只生成一个实例防止重复建立数据连接
     *
     * @param unknown $config            
     * @param string $exception            
     * @return Ambigous <unknown>
     */
    public static function daoInstance($config, $exception = true)
    {
        static $_dbInstance = [];
        $dbkey = md5(json_encode($config));
        if (! isset($_dbInstance[$dbkey])) {
            $type = strtolower($config['DB_TYPE']);
            $driver = strtolower($config['DB_DRIVE']);
            $dbDriver = '\Sfsn\Core\Dao\\' . ucfirst($type) . ucfirst($driver); // 定义数据驱动类名，包括命名空间
            $_dbInstance[$dbkey] = new $dbDriver($config, $exception); // 实例化数据库驱动类
        }
        return $_dbInstance[$dbkey];
    }

    /**
     * 统计行数
     */
    public function count()
    {
        $table = $this->options['table']; // 当前表
        $field = '*'; // 查询的字段
        $where = $this->_parseCondition(false); // 条件
        $this->sql = "SELECT count($field) FROM $table $where"; // 这不是真正执行的sql，仅作缓存的key使用
               
        $readLink = $this->readLink();
        $count = $readLink->count($table, $where, $field);       
        $this->sql = $readLink->getSql(); // 从驱动层返回真正的sql语句，供调试使用
       
        return $count;
    }
    /**
     * 分页
     * 该方法一定要放到查询条件之后
     * @param unknown $param
     */
    public function page($param)
    {   
        //保存options
        $options = $this->options;
        //获得总数
        $count = $this->count();
        //回复被count清空的options
        $this->options = $options;
        //分页配置
        $config = [
            'tag' => 'page',
            'separator' => '-',
            'nextPage' => '下一页',
            'prePage' => '上一页',
            'firstPage' => '首页',
            'lastPage' => '尾页',
            'preBar' => '<<',
            'nextBar' => '>>',
            'wrap' => '',
            'wrapClass' => 'pagewrap',
            'wrapId' => '',
            'isAjax' => false,
            'ajaxName' => '',
            'barNum' => 10,
            'total' => $count,
            'rows'=>10,
            'mode'=>1,
        ];
        if(is_array($param)){
            $config = array_merge($config, $param);
        }else{
            $config['rows'] = $param;
        }
        //实例化分页
        self::$page = new Page($config);
        $cur_page = self::$page->getCurPage(); // 当前页码        
        $limit_start = ($cur_page - 1) * $config['rows'];
        $this->options['limit'] = $limit_start . ',' . $config['rows'];
        
        return $this;
    }
    /**
     * 获取当前页
     */
    public function getCurPage()
    {
        return is_null(self::$page) ? 1 : self::$page->getCurPage();
    }
    /**
     * 获取分页字符串
     */
    public function pageString()
    {        
        return is_null(self::$page) ? '' : self::$page->show();
    }
    /**
     * 获取一条数据
     * @param string $where
     * @return Ambigous <>
     */
    public function fetch($where = null)
    {   
        $this->options['limit'] = 1;
        $data = $this->fetchAll($where);
        return $data[0];
    }
    /**
     * 获取多条数据
     * @param string $where
     * @return Ambigous <\Sfsn\Core\Model\boolean, \Sfsn\Core\Model\unknown>
     */
    public function fetchAll($where = null)
    {
        if (is_numeric($where)) {
            $field = $this->getPri();
            $this->options['where'] = [$field => $where ];
        }else{
            $this->where($where);
        }        
        $table = $this->options['table']; // 当前表
        $field = $this->options['field']; // 查询的字段
        $where = $this->_parseCondition(false); // 条件
        $this->sql = "SELECT $field FROM $table $where";
        
        return $this->query($this->sql, []);
    }

    public function update($where = null)
    {
        if (is_numeric($where)) {
            $field = $this->getPri();
            $this->options['where'] = [$field => $where ];
        }else{
            $this->where($where);
        }        
        $table = $this->options['table']; // 当前表
        $data = $this->_parseData('save');	//要更新的数据
        $where = $this->_parseCondition(ture); // 条件
        if ( empty($where) ) return false; //修改条件为空时，则返回false，避免不小心将整个表数据修改了
        	
        $this->sql = "UPDATE $table SET $data $where" ;
        $writeLink = $this->writeLink();
        $writeLink->execute($this->sql);
        return $writeLink->affectedRows();
    }

    public function updateAll()
    {
        $table = $this->options['table'];	//当前表
        $data = $this->_parseData('save');	//要更新的数据      
         
        $this->sql = "UPDATE $table SET $data " ;
        $writeLink = $this->writeLink();
        $writeLink->execute($this->sql);
        return $writeLink->affectedRows();
    }

    public function delete($where = null)
    {
        if (is_numeric($where)) {
            $field = $this->getPri();
            $this->options['where'] = [$field => $where ];
        }else{
            $this->where($where);
        }
        $table = $this->options['table']; // 当前表
        $where = $this->_parseCondition(true); // 条件
        if ( empty($where) ) return false; //修改条件为空时，则返回false，避免不小心将整个表数据修改了
        
        $this->sql = "DELETE FROM $table $where";        
        $writeLink = $this->writeLink();
        $query = $writeLink->execute($this->sql);
        return $writeLink->affectedRows();
    }

    public function deleteAll()
    {
        $table = $this->options['table']; // 当前表
        
        $this->sql = "DELETE FROM $table";
        $writeLink = $this->writeLink();
        $query = $writeLink->execute($this->sql);
        return $writeLink->affectedRows();
    }
    // 插入数据
    public function insert($data=null, $replace = false)
    {
        if (!is_null($data)) $this->options['data'] = $data;
        $table = $this->options['table']; // 当前表        
        $data = $this->_parseData('add'); // 要插入的数据
        $INSERT = $replace ? 'REPLACE' : 'INSERT';
        $this->sql = "$INSERT INTO $table $data";
    
        $writeLink = $this->writeLink();
        $query = $writeLink->execute($this->sql);
        if ($writeLink->affectedRows()) {
            $id = $writeLink->lastId();
            return empty($id) ? $writeLink->affectedRows() : $id;
        }
        return false;
    }
    
    // 替换数据
    public function replace()
    {
        return $this->insert(true);
    }
    // 回调方法，连贯操作的实现
    public function __call($method, $args)
    {
        $method = strtolower($method);
        $options = [
            'data',
            'group',
            'having',
            'order',
            'limit',
            'cache'
        ];
        if (in_array($method, $options)) {
            $this->options[$method] = $args[0]; // 接收数据
        } else {
            $this->field[$method] = $args[0];
        }
        return $this; // 返回对象，连贯查询
    }

    public function __get($property)
    {
        $output = null;
        $field = isset($this->field[$property]) ? $this->field[$property] : null;
        $output = isset($this->$property) ? $this->$property : $field;
        return $output;
    }

    public function __set($property, $value = null)
    {
        if (isset($this->$property)) {
            $this->$property = $value;
        } else {
            $this->field[$property] = $value;
        }
    }

    /**
     * 执行查找操作
     *
     * @param unknown $sql            
     * @param unknown $params            
     * @return boolean|unknown
     */
    public function query($sql, $params = [])
    {
        if (empty($sql))
            return false;
        $sql = str_replace('{pre}', $this->pre, $sql); // 表前缀替换
        $this->sql = $sql;
        
        $readLink = $this->readLink();
        $readLink->query($this->sql, $params);
        $data = $readLink->fetchAll();
        $this->sql = $readLink->getSql();        
        return $data;
    }

    /**
     * 执行增删改操作
     *
     * @param unknown $sql            
     * @param unknown $params            
     * @return boolean
     */
    public function execute($sql, $params = [])
    {
        if (empty($sql)){
            return false;
        }
        $sql = str_replace('{pre}', $this->pre, $sql); // 表前缀替换
        $this->sql = $sql;
        $writeLink = $this->writeLink();
        $query = $writeLink->execute($this->sql, $params);
        $this->sql = $writeLink->getSql();
        return $query;
    }
    
    /**
     * 获取数据库内的所有表
     * @return unknown
     */
    public function getTables()
    {
        $database = $this->config['DB_NAME'];
        $this->sql = "SHOW TABLES FROM `$database`"; // 这不是真正执行的sql，仅作缓存的key使用
                
        $writeLink = $this->writeLink();
        $data = $writeLink->getTables($database);
        $this->sql = $writeLink->getSql(); // 从驱动层返回真正的sql语句，供调试使用
        return $data;
    }
    /**
     * 获取一张表的所有字段
     * @return unknown
     */
    public function getFields()
    {
        $table = $this->options['table'];
        $this->sql = "SHOW FULL FIELDS FROM {$table}"; // 这不是真正执行的sql，仅作缓存的key使用
        
        $writeLink = $this->writeLink();
        $data = $writeLink->getFields($table);
        $this->sql = $writeLink->getSql(); // 从驱动层返回真正的sql语句，供调试使用
        return $data;
    }

    /**
     * 获取表主键
     * 
     * @return unknown
     */
    public function getPri()
    {
        $fields = $this->getFields();
        foreach ($fields as $field) {
            if ($field['primary'])
                return $field['name'];
        }
    }
    
    
    
    // 返回sql语句
    public function getSql()
    {
        return $this->sql;
    }

    /**
     * 处理where条件支持如下语法
     * where('id','>','1') => `id` > '1'
     * where('id','1') => `id` = '1'
     * where("id = 1") => id = 1
     * 以上三种会成为where数组中的一员
     * where(['id'=>1]) => ['id'=>1]
     * 以上会直接覆盖where数组
     *
     * @return \Sfsn\Core\Model\Dao
     */
    public function where()
    {
        $num = func_num_args();
        switch ($num) {
            case 3:
            case 2:
                $this->options['where'][] = func_get_args();
                break;
            case 1:
                $args = func_get_args();
                if (is_array($args[0])) {
                    $this->options['where'] = array_merge($this->options['where'], $args[0]);
                }
                if (is_string($args[0])) {
                    $this->options['where'][] = $args[0];
                }
        }
        return $this;
    }

    /**
     * 处理where条件支持如下语法
     * field('f1','f2','f3') => `f1`,`f2`,`f3`
     * field('f1,f2') => f1,f2
     * field(['f1','f2']) => f1,f2
     * field(['n1'=>'f1','n2'=>'f2']) => `f1` as n1,`f2` as n2
     *
     * 以上会直接覆盖field数组
     *
     * @return \Sfsn\Core\Model\Dao
     */
    public function field()
    {
        $num = func_num_args();
        $args = func_get_args();
        $field = [];        
        if($num >1){
            //field("year",'day',['id2'=>"count(id)"])
            //field("year",'day',"count(id)")
            foreach ($args as $val){
                if (is_array($val)){                    
                    $field = array_merge($field,$val);
                }else{
                    $field[] = $val;
                }
            }
        }else{            
            if (is_array($args[0])){
                //field(["year",'day','id2'=>"count(id)"])
                $field = array_merge($field,$args[0]);
            }else{
                //field("year,day,count(*)")
                $array = explode(',', $args[0]);
                $field = array_merge($field,$array);
            } 
        }        
        $readLink = $this->readLink();
        $this->options['field'] = $readLink->pareField($field);
        return $this;
    }

    /**
     * 设置表，$prefix为true的时候开启表前缀，false忽略表前缀
     *
     * @param unknown_type $table            
     * @param unknown_type $prefix            
     * @return unknown array('table1'=>'t1','table2'=>'t2') 变成table1 t1,table1 t2
     */
    public function table($table, $prefix = true)
    {
        $config = $this->config;
        
        $tableString = '';
        if (is_array($table)) {
            foreach ($table as $key => $val) {
                $as = is_numeric($key) ? $val : $key;
                $pre = $prefix ? $config['DB_PREFIX'] : '';
                $tableString .= $pre . "$val $as ,";
            }
            $tableString = substr($tableString, 0, - 1); // 去除最后的逗号 ,
        } else {
            $tableString .= $prefix ? $config['DB_PREFIX'] . $table : $table;
        }
        $this->options['table'] = $tableString;
        return $this;
    }
    // 事务开始
    public function beginTransaction()
    {
        $writeLink = $this->writeLink();
        $writeLink->beginTransaction();
    }
    // 事务提交
    public function commit()
    {
        $writeLink = $this->writeLink();
        $writeLink->commit();
    }
    // 事务回滚
    public function rollBack()
    {
        $writeLink = $this->writeLink();
        $writeLink->rollBack();
    }
    /**
     * 删除多余字段
     */
    private function _filter()
    {
        //字段处理对象
        $schema = $this->_schema();
        $tempData = [];
        $fields = $this->getFields();        
        $tableField = array_keys($fields);//取得数据库中字段名称
        $data = $this->options['data'];
        if(empty($data)) return ;
        foreach ($data as $key => $val){
            //二维数组时处理
            if(is_array($val)){
                foreach ($val as $k=>$v){
                    if (!in_array($k, $tableField)){
                        unset($data[$key][$k]);
                    }else{                        
                        $data[$key][$k] = $schema->getValue($fields[$k],$v);
                    }
                }
            }else {
                //一维数组处理
                if (!in_array($key, $tableField)){
                    unset($data[$key]);
                }else{                    
                    $data[$key] = $schema->getValue($fields[$key],$val);
                }
            }            
        }
        $this->options['data'] = $data;
    }
    /**
     * 去的字段处理类对象
     * @return unknown
     */
   private function _schema()
   {
       $dbType = Config::get('DB_TYPE');
       $class = '\Sfsn\Core\Schema\\'.$dbType."Schema";
       return new $class();
   }
    /**
     * 解析数据
     * $type add 添加 save 修改
     *
     * @param unknown $type            
     * @return unknown
     */
    private function _parseData($type)
    {
        $writeLink = $this->writeLink();
        //过滤多余字段元素
        $this->_filter();
        
        //生成sql
        if($type == 'add'){
            $data = $writeLink->parseAdd($this->options['data']);
        }else{
            $data = $writeLink->parseUpdate($this->options['data']);
        }       
        
        $this->options['data'] = '';
        return $data;
    }

    /**
     * 查询条件处理
     * $isMaster true主服务器 false从服务器
     *
     * @param string $isMaster            
     * @return unknown
     */
    private function _parseCondition($isMaster = true)
    {        
        // 主数据库用写链接 从数据库用读链接
        $link = $isMaster ? $this->writeLink() : $this->readLink();
        $condition = $link->parseCondition($this->options);
        $this->options['where'] = '';
        $this->options['group'] = '';
        $this->options['having'] = '';
        $this->options['order'] = '';
        $this->options['limit'] = '';
        $this->options['field'] = '*';
        return $condition;
    }

    /**
     * 魔术引号处理
     *
     * @param unknown $value            
     * @return array|string
     */
    private function _escape($value)
    {
        if (is_array($value)) {
            return array_map([
                $this,
                '_escape'
            ], $value);
        } else {
            return addslashes($value);
        }
    }
    
}
